#include "Cloth.h";

#ifndef __Common_h__
  #include "Common.h"
#endif //__Common_h__

#ifndef __Particle_h__
  #include "Particle.h"
#endif //__Particle_h__

#ifndef __Spring_h__
  #include "Spring.h"
#endif //__Spring_h__

Particle* Cloth::getParticle(int x, int y) 
{
	return &m_particles[y*m_nNumParticlesWidth + x];
}

void Cloth::makeSpring(Particle *p1, Particle *p2) 
{
	m_springs.push_back(Spring(p1,p2));
}

vec3 Cloth::calcTriangleNormal(Particle *p1,Particle *p2,Particle *p3)
{
	vec3 pos1 = p1->getPosition();
	vec3 pos2 = p2->getPosition();
	vec3 pos3 = p3->getPosition();

	vec3 v1 = pos2-pos1;
	vec3 v2 = pos3-pos1;

	return cross(v1, v2);
}

void Cloth::addWindForcesForTriangle(Particle *p1,Particle *p2,Particle *p3, const vec3 wind)
{
	vec3 normal = calcTriangleNormal(p1,p2,p3);
	vec3 normalizedNormal = normalize(normal);
	vec3 force = normal *dot(normalizedNormal, wind);
	p1->addForce(force);
	p2->addForce(force);
	p3->addForce(force);
}

Particle& Cloth::getParticleByID(float id)
{
	std::hash_map <float, Particle*> :: iterator acIter, rcIter;
	rcIter = m_mapPartIdToPartPointer.find(id);
	return *rcIter->second;
}

Cloth::Cloth(float width, float height, int numParticlesWidth, int numParticlesHeight, int clothHangMode) : 
	m_nNumParticlesWidth(numParticlesWidth), 
	m_nNumParticlesHeight(numParticlesHeight),
	m_nNumSpringIterations(DEFAULT_SPRING_ITERATIONS)
{
	m_particles.resize(m_nNumParticlesWidth*m_nNumParticlesHeight);

	m_pLog = fopen("log", "w");

	// creating particles in a grid of particles from (-width/2,height/2,0) to (width/2,-height/2,0)
	for(int x=0; x<m_nNumParticlesWidth; x++)
	{
		for(int y=0; y<m_nNumParticlesHeight; y++)
		{
			vec3 position = vec3(width * (x/(float)m_nNumParticlesWidth) - width/2.0f,
							     -height * (y/(float)m_nNumParticlesHeight) + height/2.0f,
							     0);
			Particle p(position);
			m_particles[y*m_nNumParticlesWidth+x]=p;  // inserting new particle in the column x at row y
			//
			std::pair<float,Particle*> myPair (m_particles[y*m_nNumParticlesWidth+x].getID(), &m_particles[y*m_nNumParticlesWidth+x]);
			m_mapPartIdToPartPointer.insert(myPair);

			//fprintf(m_pLog,"%4.2f, %4.2f\n", position.x, position.y);
		}
	}

	//fclose(m_pLog);

	// Creating springs between immediate neighbouring particles (distance = 1)
	for(int x=0; x<m_nNumParticlesWidth; x++)
	{
		for(int y=0; y<m_nNumParticlesHeight; y++)
		{
			if (x<m_nNumParticlesWidth-1)                              makeSpring(getParticle(x,y),   getParticle(x+1,y));
			if (y<m_nNumParticlesHeight-1)                             makeSpring(getParticle(x,y),   getParticle(x,y+1));
			if (x<m_nNumParticlesWidth-1 && y<m_nNumParticlesHeight-1) makeSpring(getParticle(x,y),   getParticle(x+1,y+1));
			if (x<m_nNumParticlesWidth-1 && y<m_nNumParticlesHeight-1) makeSpring(getParticle(x+1,y), getParticle(x,y+1));
		}
	}


	// Creating springs between particles of distance = 2
	for(int x=0; x<m_nNumParticlesWidth; x++)
	{
		for(int y=0; y<m_nNumParticlesHeight; y++)
		{
			if (x<m_nNumParticlesWidth-2)                              makeSpring(getParticle(x,y),   getParticle(x+2,y));
			if (y<m_nNumParticlesHeight-2)                             makeSpring(getParticle(x,y),   getParticle(x,y+2));
			if (x<m_nNumParticlesWidth-2 && y<m_nNumParticlesHeight-2) makeSpring(getParticle(x,y),   getParticle(x+2,y+2));
			if (x<m_nNumParticlesWidth-2 && y<m_nNumParticlesHeight-2) makeSpring(getParticle(x+2,y), getParticle(x,y+2));			
		}
	}

	//-----------------------------------------------------------------
	// Setting unmovable particles based on clothHangMode 
	//-----------------------------------------------------------------

	if (clothHangMode == 0)
	{
		// making the upper left most three and right most three particles unmovable
		for(int i=0;i<3; i++)
		{
			getParticle(i ,0)->offsetPosition(vec3(1.5f, 0.0f, 0.0f)); // moving the particle a bit towards the center, to make it hang more natural 
			getParticle(i ,0)->makeUnmovable(); 

			getParticle(m_nNumParticlesWidth-1-i ,0)->offsetPosition(vec3(-0.5f, 0.0f, 0.0f)); // moving the particle a bit towards the center, to make it hang more natural
			getParticle(m_nNumParticlesWidth-1-i ,0)->makeUnmovable();
		}
	}

	if (clothHangMode == 1)
	{
		// Making cloth's top in the middle unmovable 
		getParticle(m_nNumParticlesWidth/2 ,0)->makeUnmovable(); 
	}

	if (clothHangMode == 2)
	{// Making the left most column of particles unmovable
		for (int i=0; i<m_nNumParticlesHeight; i++)
		{
			getParticle(0 ,i)->makeUnmovable(); 
		}
	}

	if (clothHangMode == 3)
	{// Making right most column of particles unmovable
		for (int i=0; i<m_nNumParticlesHeight; i++)
		{
			getParticle(m_nNumParticlesWidth-1 ,i)->makeUnmovable(); 
		}
	}
}

Cloth::~Cloth()
{}

void Cloth::printParticle(const vertexData& vd)
{
		fclose(m_pLog);
		m_pLog = fopen("log", "a+");

		fprintf(m_pLog, "%10.6f:%10.6f:%10.6f \t %10.6f:%10.6f:%10.6f \t %10.6f:%10.6f:%10.6f\n",	
															vd.position.x, vd.position.y, vd.position.z, 
															vd.normal.x,   vd.normal.y,   vd.normal.z,
															vd.color.x,    vd.color.y,    vd.color.z);

		fclose(m_pLog);
}

void Cloth::pushVertexToVertexBuffer(Particle* p, const vec3 color)
{
	vertexData tmp;
	tmp.id = p->getID();
	tmp.position = p->getPosition();
	tmp.normal = normalize(p->getNormal());
	tmp.color = color;
	tmp.acceleration = p->getAcceleration();
	tmp.old_position = p->getOldPosition();

	m_vertexBuffer.push_back(tmp);
	//printParticle(tmp);
}

/* Filling the 'vertexBuffer' vector of particles with triangle data as following:
   for each four particles, 2 triangles are formed as below:

	(x,y)   *--* (x+1,y)
			| /|
			|/ |
	(x,y+1) *--* (x+1,y+1)
*/

void Cloth::pushTriangleVerticesToVertexBuffer(Particle *p1, Particle *p2, Particle *p3, const vec3 color)
{
	pushVertexToVertexBuffer(p1, color);
	pushVertexToVertexBuffer(p2, color);
	pushVertexToVertexBuffer(p3, color);
}

void Cloth::refreshVertexBuffer()
{
	m_vertexBuffer.clear();

	for(int x=0; x<m_nNumParticlesWidth-1; x++)
	{
		for(int y=0; y<m_nNumParticlesHeight-1; y++)
		{
			// Altering the color based on the column number
			vec3 color(0, 0, 0);
			if (x%2)
				color = vec3(1.0f,0.0f,0.0f);
			else
				color = vec3(1.0f,1.0f,0.0f);

			pushTriangleVerticesToVertexBuffer(getParticle(x+1,y),   getParticle(x,y),   getParticle(x,y+1), color);
			pushTriangleVerticesToVertexBuffer(getParticle(x+1,y+1), getParticle(x+1,y), getParticle(x,y+1), color);
		}
	}
}

void Cloth::applyAllPrings()
{
	std::vector<Spring>::iterator spring;
	for(int i=0; i<m_nNumSpringIterations; i++) 
	{
		for(spring = m_springs.begin(); spring != m_springs.end(); spring++ )
		{
			(*spring).applySpring();
		}
	}
}

void Cloth::timeStep(float damping)
{
	std::vector<Particle>::iterator particle;
	for(particle = m_particles.begin(); particle != m_particles.end(); particle++)
	{
		(*particle).timeStep(damping);
	}
}

void Cloth::addForce(const vec3 direction)
{
	std::vector<Particle>::iterator particle;
	for(particle = m_particles.begin(); particle != m_particles.end(); particle++)
	{
		(*particle).addForce(direction); // adds the force to each particle
	}

}

void Cloth::windForce(const vec3 wind, float scale)
{
	for(int x = 0; x<m_nNumParticlesWidth-1; x++)
	{
		for(int y=0; y<m_nNumParticlesHeight-1; y++)
		{
			addWindForcesForTriangle(getParticle(x+1,y),getParticle(x,y),getParticle(x,y+1),wind*scale);
			addWindForcesForTriangle(getParticle(x+1,y+1),getParticle(x+1,y),getParticle(x,y+1),wind*scale);
		}
	}
}

bool Cloth::checkBoundingSphereCollision(const vec3 center,const float radius )
{
	bool changed = false;
	std::vector<Particle>::iterator particle;
	for(particle = m_particles.begin(); particle != m_particles.end(); particle++)
	{
		vec3 v = (*particle).getPosition()-center;
		float l = length(v);
		if ( length(v) < radius) // if the particle is inside the bounding sphere
		{
			(*particle).offsetPosition(normalize(v)*(radius-l)); // projecting the particle to the surface of the bounding sphere
			changed = true;
		}
	}
	return changed;
}

void Cloth::updateParticlesFromTransFeed(PostTransFeedVertexData* postTransData, int len)
{
	resetPerParticleAccelAddedInd();

	std::hash_map <float, Particle*> :: iterator iter;

	for (int i=0; i<len; i++)
	{
		iter = m_mapPartIdToPartPointer.find(postTransData[i].id);

		if ( iter == m_mapPartIdToPartPointer.end( ) )
		{
			printf("id %d wasn't found in the hash map\n", postTransData[i].id);
			continue;
		}

		Particle* p = iter->second;

		if (p->isMovable())
		{
			// Adding Gravity Force's acceleration
			if (!p->isUpdatedFromTransFeed())
			{
				p->setAcceleration(postTransData[i].acceleration_particle);
				p->setFirstTimeUpdFromTransFeed(true);

				// Updating Position and Old Position
				p->setPosition(postTransData[i].position);
				p->setOldPosition(postTransData[i].old_position);
			}

			// Adding Wind Force's acceleration
			p->setAcceleration(p->getAcceleration() + postTransData[i].acceleration_triangle);
		}
	}
}

void Cloth::resetPerParticleAccelAddedInd()
{
	std::vector<Particle>::iterator particle;
	for(particle = m_particles.begin(); particle != m_particles.end(); particle++)
	{
		(*particle).resetFirstTimeUpdFromTransFeed();
	}
}

void Cloth::incrSpringIterations(int num)
{
	m_nNumSpringIterations+= num; 
	
	if (m_nNumSpringIterations<2)
	{
		m_nNumSpringIterations = 2;
	}
}

int Cloth::getNumOfTriangles()
{
	return (m_nNumParticlesWidth-1)*(m_nNumParticlesHeight-1)*6;
}